﻿namespace EasyToolkit.Excel.BindingTemplates.Controls.Button
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Reflection;
    using EasyToolkit.BindingTemplates.Context;
    using EasyToolkit.BindingTemplates.Definitions.Binding;
    using EasyToolkit.Excel.BindingTemplates.Definitions;
    using EasyToolkit.Tools.Extensions;
    using EasyToolkit.Tools.Reflection;

    class ExcelBindingDefinitionButton : BindingDefinition
    {
        #region attributes and properties
        public const string BUTTON_TEMPLATE_PREFIX = "<Button";

        public IBindingDefinition LabelBindingDefinition
        { get; private set; }

        public ExcelButtonDefinition Definition
        { get; private set; }

        public ExcelTemplateDefinitionPart TemplateDefinition
        { get; private set; }

        public MethodInfo Command
        { get; private set; }

        public bool OnClickWithRange
        { get; private set; }

        public PropertyInfo EnablePropertyInfo
        { get; private set; }
        #endregion

        #region .ctors and factories
        private ExcelBindingDefinitionButton(ExcelTemplateDefinitionPart templateDefinition, ExcelButtonDefinition definition)
        {
            TemplateDefinition = templateDefinition;
            Definition = definition;
            DefinitionDescription = BindingDefinitionDescription.CreateBindingDescription(Definition.Label, Definition.Label);
            if (! string.IsNullOrEmpty(definition.Label))
            {
                LabelBindingDefinition = BindingDefinitionFactory.CreateCommonBindingDefinition(templateDefinition, DefinitionDescription);
                CanNotify = LabelBindingDefinition.CanNotify; 
            }
            RetrieveOnClickMethod();
            RetrieveEnableProperty();
        }

        static public ExcelBindingDefinitionButton CreateInstance(ExcelTemplateDefinitionPart templateDefinition, string definition)
        {
            ExcelBindingDefinitionButton ret = null;
            if (! string.IsNullOrEmpty(definition))
            {
                try
                {
                    ExcelButtonDefinition excelButtonDefinition = definition.Deserialize<ExcelButtonDefinition>();
                    ret = new ExcelBindingDefinitionButton(templateDefinition, excelButtonDefinition);
                }
                catch (Exception ex)
                {
                    string message = string.Format("Cannot retrieve the button definition '{0}'. {1}", definition.EmptyIfNull(), ex.Message);
                    throw new EasyToolkitException(message, ex);
                }
            }
            return ret;
        }
        #endregion

        #region public methods
        public override IBindingContextItem ContextItemFactory(IBindingContextElement parent)
        {
            return new ExcelContextItemButton(parent, this);
        }

        public override object UpdateDataSource(object dataSource, object data)
        {
            return null;
        }

        public override object ResolveBinding(object dataSource)
        {
            return LabelBindingDefinition == null ? null : LabelBindingDefinition.ResolveBinding(dataSource);
        }

        public override IEnumerable<INotifyPropertyChanged> GetObjectsToNotify(object dataSource)
        {
            return LabelBindingDefinition == null ? null : LabelBindingDefinition.GetObjectsToNotify(dataSource);
        }

        public override bool MustNotify(object dataSource, object source, PropertyChangedEventArgs args)
        {
            return LabelBindingDefinition == null ? false : LabelBindingDefinition.MustNotify(dataSource, source, args);
        }
        #endregion

        #region private methods
        private void RetrieveOnClickMethod()
        {
            if (!string.IsNullOrEmpty(Definition.Command))
            {
                try
                {
                    string onCommand = Definition.Command.Trim();
                    string[] commandElements = onCommand.Split(',');

                    Type type;
                    MethodInfo method;
                    string methodName;
                    if (commandElements.Count() != 1 && commandElements.Count() != 3)
                        throw new ArgumentException("The 'Command' separator is ',' and it must be composed this way 'Assembly,Type,Method' or, if the method is part of the calling instance, 'Method'");

                    if (commandElements.Count() == 1)
                    {
                        methodName = commandElements[0].EmptyIfNull().Trim();
                        type = TemplateDefinition.MainBindingDefinition.BindingTypeIsGeneric ? TemplateDefinition.MainBindingDefinition.BindingGenericType : TemplateDefinition.MainBindingDefinition.BindingType;
                    }
                    else
                    {
                        methodName = commandElements[2].EmptyIfNull().Trim();
                        type = TypeHelpers.GetType(commandElements[0], commandElements[1]);
                    }

                    method = type.GetMethod(methodName);
                    if (method == null)
                        throw new ArgumentException(string.Format("Method '{0}' not found", methodName));

                    ParameterInfo[] parameters = method.GetParameters();
                    if (method.IsStatic)
                    {
                        if (parameters.Count() > 2)
                            throw new EasyToolkitException(string.Format("Method definition must be 'void static {0}(object currentObject [, Range <currentObject range>]'", methodName));

                        OnClickWithRange = parameters.Count() == 2;
                    }
                    else
                    {
                        if (parameters.Count() > 1 || (parameters.Count() == 1 && !parameters[0].ParameterType.FullName.Equals("Microsoft.Office.Interop.Excel.Range")))
                            throw new EasyToolkitException(string.Format("Method definition must be 'void {0}([Range <currentObject range>])'", methodName));

                        OnClickWithRange = parameters.Count() == 1;
                    }
                    Command = method;
                }
                catch (Exception ex)
                {
                    throw new EasyToolkitException("Get 'Command' methodInfo information failed", ex);
                }
            }
        }

        private void RetrieveEnableProperty()
        {
            if (!string.IsNullOrEmpty(Definition.EnableProp))
            {
                try
                {
                    string enableProp = Definition.EnableProp.Trim();
                    string[] enablePropElements = enableProp.Split(',');
                    if (enablePropElements.Count() != 1 && enablePropElements.Count() != 3)
                        throw new ArgumentException("The 'EnableProp' separator is ',' and it must be composed this way 'Assembly,Type,Property' or, if the property is part of the calling instance, 'Property'");

                    Type type;
                    string propertyName;
                    if (enablePropElements.Count() == 1)
                    {
                        propertyName = enablePropElements[0].EmptyIfNull().Trim();
                        type = TemplateDefinition.MainBindingDefinition.BindingTypeIsGeneric ? TemplateDefinition.MainBindingDefinition.BindingGenericType : TemplateDefinition.MainBindingDefinition.BindingType;
                    }
                    else
                    {
                        propertyName = enablePropElements[2].EmptyIfNull().Trim();
                        if (!string.IsNullOrEmpty(enablePropElements[0]) && !string.IsNullOrEmpty(enablePropElements[1]))
                            type = TypeHelpers.GetType(enablePropElements[0], enablePropElements[1]);
                        else
                            type = TemplateDefinition.MainBindingDefinition.BindingTypeIsGeneric ? TemplateDefinition.MainBindingDefinition.BindingGenericType : TemplateDefinition.MainBindingDefinition.BindingType;
                    }


                    if (!string.IsNullOrEmpty(enablePropElements[0]) && !string.IsNullOrEmpty(enablePropElements[1]))
                        type = TypeHelpers.GetType(enablePropElements[0], enablePropElements[1]);
                    else
                        type = TemplateDefinition.MainBindingDefinition.BindingTypeIsGeneric ? TemplateDefinition.MainBindingDefinition.BindingGenericType : TemplateDefinition.MainBindingDefinition.BindingType;

                    EnablePropertyInfo = type.GetProperty(enablePropElements[2]);
                    if (EnablePropertyInfo == null)
                        throw new ArgumentException(string.Format("Property '{0}' not found", enablePropElements[2]));
                }
                catch (Exception ex)
                {
                    throw new EasyToolkitException("Get 'EnableProp' property information failed", ex);
                }
            }
        }
        #endregion
    }
}
